openapi: 3.1.0
info:
  title: Cloud Storage Open API
  description: >
    Quản lý tệp, lượt tải lên, lượt xóa và nhãn tệp của cloud storage MoreLogin
    thông qua MoreLogin Open API.


    **URL cơ sở**: `https://api.morelogin.com`


    Xác thực được thực hiện bằng token Bearer OAuth2. Xem [Xác
    thực](../Getting%20Started/authentication.md) để biết chi tiết.
  version: '2026-09-05'
servers:
  - url: https://api.morelogin.com
tags:
  - name: Cloud Storage
    description: Truy vấn hạn mức và mức sử dụng lưu trữ
  - name: Cloud Storage Files
    description: Truy vấn, tải lên và xóa tệp trên lưu trữ đám mây
  - name: Cloud Storage File Tags
    description: Quản lý các thẻ gắn với tệp trên lưu trữ đám mây
x-tagGroups:
  - name: Cloud Storage
    tags:
      - Cloud Storage
      - Cloud Storage Files
      - Cloud Storage File Tags
paths:
  /cloudstorage/info:
    get:
      summary: Truy vấn thông tin lưu trữ đám mây
      description: >-
        Trả về hạn mức cloud storage của nhóm và mức sử dụng hiện tại.

        - **Điều kiện tiên quyết**: nhóm phải đã có bản ghi cloud storage. **Một
        nhóm chưa từng được cấp cloud storage sẽ nhận về lỗi, không phải giá trị
        dung lượng bằng 0** — bản ghi được tạo khi mua dung lượng, không phải
        được tạo trễ bởi lệnh gọi này.

        - **Tác động**: không có. Chỉ đọc. An toàn khi thử lại với thời gian chờ
        tăng dần có giới hạn.

        - **Đơn vị**: `capacity` và `usedCapacity` là số byte.

        - **Độ chính xác**: `usedCapacity` là một bộ đếm tích lũy được cập nhật
        khi tải lên và khi xóa, không phải một phép tổng tính trực tiếp trên
        bảng tệp, nên nó có thể lệch so với tổng `fileSize` trong
        `/cloudstorage/file/page`.

        - **Giá trị**: `capacityStatus` chỉ bằng `1` khi một hạn mức dương đã bị
        dùng hết; `isExpired` so sánh ngày hết hạn với thời điểm hiện tại. Những
        tệp chưa từng được xác nhận tải lên không được tính vào `usedCapacity`.


        Các mã lỗi nghiệp vụ đã ghi nhận: `39001`. Xem [Error
        Codes](../Getting%20Started/error-codes.md) để biết ý nghĩa từng mã. Mọi
        thao tác cũng có thể trả về các mã dùng chung.
      operationId: queryCloudStorageInfo
      tags:
        - Cloud Storage
      responses:
        '200':
          $ref: '#/components/responses/StorageInfoResponse'
  /cloudstorage/file/page:
    post:
      summary: Phân trang tệp trên lưu trữ đám mây
      description: >-
        Liệt kê theo trang các tệp Cloud Storage.

        - **Tác động**: không có. Chỉ đọc. An toàn khi thử lại với thời gian chờ
        tăng dần có giới hạn.

        - **Khả năng nhìn thấy**: **chỉ những tệp đã xác nhận tải lên mới được
        liệt kê** — một tệp có URL đã ký trước nhưng chưa bao giờ hoàn tất sẽ
        không xuất hiện. Thành viên không có quyền xem toàn bộ dữ liệu chỉ thấy
        những tệp do chính mình tải lên.

        - **Bộ lọc**: `fileName` khớp một phần; `tagIds` khớp theo logic HOẶC;
        kết quả được sắp từ mới đến cũ và không thể đổi thứ tự này.

        - **Lưu ý**: một phần tử trong `tagInfo` có thể bằng null khi một thẻ bị
        xóa trong lúc vẫn còn gắn với tệp.
      operationId: pageCloudStorageFiles
      tags:
        - Cloud Storage Files
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FilePageRequest'
            example:
              pageNo: 1
              pageSize: 20
              fileName: report
              tagIds:
                - '1800000000000001'
      responses:
        '200':
          $ref: '#/components/responses/FilePageResponse'
  /cloudstorage/upload/init:
    post:
      summary: Yêu cầu URL tải lên có ký trước theo lô
      description: >-
        Đặt trước các bản ghi cloud storage và lấy URL tải lên đã ký trước.

        - **Điều kiện tiên quyết**: nhóm phải có bản ghi cloud storage và bản
        ghi đó không được hết hạn. Phần tên cơ sở của mỗi tệp giới hạn 60 ký tự
        và phải là duy nhất cả trong phạm vi yêu cầu lẫn so với mọi tệp đang có
        của nhóm — **việc đối chiếu dựa trên tên không kèm phần mở rộng**, nên
        `a.png` sẽ xung đột với `a.mp4` đã tồn tại.

        - **Giới hạn**: toàn nhóm chỉ được có tối đa 100 lượt tải lên chưa xác
        nhận cùng lúc.

        - **Tác động**: chèn một bản ghi cho mỗi tên tệp với trạng thái đang tải
        lên và kích thước 0, rồi trả về `{id, fileName, presignedUrl}` cho từng
        tệp.

        - **Hạn mức**: **dung lượng không được kiểm tra ở bước này.** Nó bị trừ
        khi bạn xác nhận lượt tải lên, nên init có thể thành công mà complete
        vẫn thất bại vì hết dung lượng.

        - **Hoàn tất**: đồng bộ. **Mỗi URL đã ký trước có hiệu lực trong một
        giờ**; hãy `PUT` dữ liệu tệp lên đó, sau đó gọi
        `/cloudstorage/upload/complete`.


        Các mã lỗi nghiệp vụ đã ghi nhận: `39001`, `39037`, `39044`, `39045`,
        `39047`, `39050`, `39051`. Xem [Error
        Codes](../Getting%20Started/error-codes.md) để biết ý nghĩa từng mã. Mọi
        thao tác cũng có thể trả về các mã dùng chung.
      operationId: batchRequestCloudStoragePresignedUploadUrls
      tags:
        - Cloud Storage Files
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UploadInitRequest'
            example:
              fileNames:
                - example.jpg
                - docs/report.pdf
      responses:
        '200':
          $ref: '#/components/responses/UploadInitResponse'
  /cloudstorage/upload/complete:
    post:
      summary: Xác nhận hoàn tất tải lên
      description: >-
        Xác nhận một tệp cloud storage đã được tải lên.

        - **Điều kiện tiên quyết**: bản ghi tệp phải thuộc nhóm của bạn và đối
        tượng phải đã nằm trong bộ lưu trữ — dịch vụ đọc ETag và độ dài của nó
        để kiểm chứng. Kích thước phải lớn hơn 0 và nhỏ hơn 2 GB.

        - **Tác động**: trừ hạn mức lưu trữ của nhóm một cách nguyên tử, rồi
        đánh dấu bản ghi là đã tải lên và lưu kích thước thật. Cho đến khi việc
        này thành công, tệp **không xuất hiện trong `/cloudstorage/file/page`**
        và vẫn chiếm một trong 100 suất chờ xác nhận.

        - **Khi thất bại, đối tượng đã tải lên sẽ bị xóa.** Gọi quá sớm, vượt
        giới hạn kích thước, hay không còn dung lượng đều khiến dữ liệu bị xóa
        khỏi bộ lưu trữ và bản ghi ở lại trạng thái chưa xác nhận. Cách khắc
        phục là `/cloudstorage/upload/init` mới rồi tải lên lại, không phải thử
        lại lệnh gọi này.

        - **Hoàn tất**: đồng bộ và idempotent sau khi đã thành công — lần gọi
        thứ hai trả về cùng tệp mà không trừ hạn mức thêm lần nữa.

        - **Lưu ý**: bản ghi không bao giờ được xác nhận sẽ tự động được dọn sau
        24 giờ.


        Các mã lỗi nghiệp vụ đã ghi nhận: `39001`, `39011`, `39014`, `39041`,
        `39046`, `39047`. Xem [Error Codes](../Getting%20Started/error-codes.md)
        để biết ý nghĩa từng mã. Mọi thao tác cũng có thể trả về các mã dùng
        chung.
      operationId: confirmCloudStorageUploadComplete
      tags:
        - Cloud Storage Files
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UploadCompleteRequest'
            example:
              id: '1800000000000001'
      responses:
        '200':
          $ref: '#/components/responses/UploadCompleteResponse'
  /cloudstorage/file/delete/batch:
    post:
      summary: Xóa tệp trên lưu trữ đám mây theo lô
      description: >-
        Xóa các tệp Cloud Storage theo ID.

        - **Điều kiện tiên quyết**: mọi ID phải tra cứu được trong nhóm của bạn;
        **nếu chỉ một ID không tra cứu được thì cả lô bị từ chối và hoàn tác**,
        kể cả những ID đã bị xóa trước đó.

        - **Tác động**: đây là xóa mềm. Dung lượng được giải phóng trong cùng
        một giao dịch, và các đối tượng đã lưu được xóa theo cơ chế nỗ lực tối
        đa — lỗi phía lưu trữ được ghi log nhưng không làm lệnh gọi thất bại,
        nên một đối tượng có thể tồn tại lâu hơn bản ghi của nó. Các liên kết
        thẻ không được dọn dẹp.

        - **Hoàn tất**: đồng bộ. Hãy xác nhận dung lượng đã giải phóng bằng
        `/cloudstorage/info`.


        Các mã lỗi nghiệp vụ đã ghi nhận: `39011`. Xem [Error
        Codes](../Getting%20Started/error-codes.md) để biết ý nghĩa từng mã. Mọi
        thao tác cũng có thể trả về các mã dùng chung.
      operationId: batchDeleteCloudStorageFiles
      tags:
        - Cloud Storage Files
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FileDeleteRequest'
            example:
              ids:
                - '1800000000001001'
                - '1800000000001002'
      responses:
        '200':
          $ref: '#/components/responses/VoidDataResponse'
  /cloudstorage/file/tag/set:
    post:
      summary: Đặt thẻ cho tệp
      description: >-
        Thay thế toàn bộ thẻ của một tệp Cloud Storage.

        - **Điều kiện tiên quyết**: mỗi lần gọi một `fileId`, và `tagIds` không
        được rỗng và phải tham chiếu tới các thẻ Cloud Storage thuộc nhóm của
        bạn. Thẻ của profile trình duyệt sẽ bị từ chối.

        - **Tác động**: các thẻ không có trong yêu cầu sẽ bị bỏ khỏi tệp và
        những thẻ còn thiếu sẽ được thêm vào. **Ở đây không có cách nào bỏ hết
        thẻ** vì danh sách rỗng bị từ chối.

        - **Hoàn tất**: đồng bộ và idempotent với cùng một request body.


        Các mã lỗi nghiệp vụ đã ghi nhận: `39011`, `39048`, `39049`. Xem [Error
        Codes](../Getting%20Started/error-codes.md) để biết ý nghĩa từng mã. Mọi
        thao tác cũng có thể trả về các mã dùng chung.
      operationId: setCloudStorageFileTags
      tags:
        - Cloud Storage File Tags
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FileTagChangeRequest'
            example:
              fileId: '1800000000001001'
              tagIds:
                - '1800000000000101'
                - '1800000000000102'
      responses:
        '200':
          $ref: '#/components/responses/VoidDataResponse'
  /cloudstorage/file/tag/add:
    post:
      summary: Thêm thẻ cho tệp
      description: >-
        Thêm thẻ cho một tệp Cloud Storage mà không bỏ các thẻ hiện có.

        - **Điều kiện tiên quyết**: mỗi lần gọi một `fileId`, và `tagIds` không
        được rỗng và phải tham chiếu tới các thẻ Cloud Storage thuộc nhóm của
        bạn.

        - **Tác động**: chỉ thêm; các thẻ hiện có được giữ nguyên. Dùng biến thể
        thay thế nếu bạn muốn thay hết.

        - **Hoàn tất**: đồng bộ và idempotent — thêm một thẻ mà tệp đã có thì
        không thay đổi gì.


        Các mã lỗi nghiệp vụ đã ghi nhận: `39011`, `39048`, `39049`. Xem [Error
        Codes](../Getting%20Started/error-codes.md) để biết ý nghĩa từng mã. Mọi
        thao tác cũng có thể trả về các mã dùng chung.
      operationId: addCloudStorageFileTags
      tags:
        - Cloud Storage File Tags
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FileTagChangeRequest'
            example:
              fileId: '1800000000001001'
              tagIds:
                - '1800000000000103'
      responses:
        '200':
          $ref: '#/components/responses/VoidDataResponse'
  /cloudstorage/file/tag/query:
    post:
      summary: Truy vấn thẻ của tệp
      description: >-
        Trả về các thẻ đang gắn với một hoặc nhiều tệp Cloud Storage.

        - **Điều kiện tiên quyết**: mọi `fileId` phải tra cứu được trong nhóm
        của bạn; **một ID không xác định sẽ làm cả lệnh gọi thất bại** chứ không
        trả về dữ liệu một phần.

        - **Tác động**: không có. Chỉ đọc. An toàn khi thử lại với thời gian chờ
        tăng dần có giới hạn.

        - **Lưu ý**: `tagIds` được đọc từ các liên kết, còn chi tiết thẻ lấy từ
        các thẻ hiện hữu, nên một thẻ bị xóa trong lúc vẫn còn liên kết sẽ xuất
        hiện trong `tagIds` mà không có mục tương ứng ở phần chi tiết. Độ dài
        hai danh sách có thể khác nhau.
      operationId: queryCloudStorageFileTags
      tags:
        - Cloud Storage File Tags
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FileTagQueryRequest'
            example:
              fileIds:
                - '1800000000001001'
      responses:
        '200':
          $ref: '#/components/responses/FileTagsResponse'
  /cloudstorage/tag/all:
    get:
      summary: Truy vấn tất cả thẻ tệp cloud storage
      description: >-
        Trả về mọi thẻ tệp Cloud Storage trong nhóm.

        - **Tác động**: không có. Chỉ đọc. An toàn khi thử lại với thời gian chờ
        tăng dần có giới hạn.

        - **Lưu ý**: endpoint không phân trang, và thẻ Cloud Storage không có
        giới hạn số lượng phía server, nên phản hồi sẽ lớn dần theo số thẻ mà
        nhóm bạn tạo.
      operationId: queryAllCloudStorageFileTags
      tags:
        - Cloud Storage File Tags
      responses:
        '200':
          $ref: '#/components/responses/TagListResponse'
  /cloudstorage/tag/create:
    post:
      summary: Tạo thẻ tệp cloud storage
      description: >-
        Tạo một thẻ tệp Cloud Storage.

        - **Điều kiện tiên quyết**: `tagName` không được để trống.

        - **Tác động**: tạo thẻ với một màu chọn từ bảng màu cố định. **Tên
        không được kiểm tra trùng lặp và không có giới hạn số lượng**, nên gọi
        lại sẽ tạo ra một thẻ thứ hai cùng tên.

        - **Hoàn tất**: đồng bộ; phản hồi mang thẻ mới.
      operationId: createCloudStorageFileTag
      tags:
        - Cloud Storage File Tags
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TagCreateRequest'
            example:
              tagName: Invoice
      responses:
        '200':
          $ref: '#/components/responses/TagResponse'
  /cloudstorage/tag/edit:
    post:
      summary: Sửa thẻ tệp cloud storage
      description: >-
        Đổi tên một thẻ tệp Cloud Storage.

        - **Điều kiện tiên quyết**: thẻ phải tồn tại trong nhóm của bạn và phải
        là thẻ Cloud Storage.

        - **Tác động**: chỉ tên thay đổi; màu và mọi liên kết hiện có đều được
        giữ, nên các tệp hiển thị tên mới ngay lập tức.

        - **Hoàn tất**: đồng bộ. Không có khóa lạc quan — các lần đổi tên đồng
        thời sẽ lấy lần ghi cuối cùng.
      operationId: editCloudStorageFileTag
      tags:
        - Cloud Storage File Tags
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TagEditRequest'
            example:
              id: '1800000000000101'
              tagName: Invoice 2026
      responses:
        '200':
          $ref: '#/components/responses/BooleanDataResponse'
  /cloudstorage/tag/delete/batch:
    post:
      summary: Xóa thẻ tệp cloud storage
      description: >-
        Xóa các thẻ tệp Cloud Storage.

        - **Điều kiện tiên quyết**: mọi ID phải là thẻ Cloud Storage trong nhóm
        của bạn; **một ID không hợp lệ sẽ làm cả lô thất bại**.

        - **Tác động**: các thẻ bị xóa nhưng **liên kết của chúng với tệp không
        được dọn dẹp**. Hãy lường trước hai hệ quả:
        `/cloudstorage/file/tag/query` vẫn tiếp tục trả về ID thẻ đã xóa mà
        không có chi tiết tương ứng, và `tagInfo` trong
        `/cloudstorage/file/page` có thể chứa phần tử null. Lọc tệp theo ID của
        một thẻ đã xóa sẽ không trả về gì.

        - **Hoàn tất**: đồng bộ, và không được bọc trong một giao dịch.
      operationId: deleteCloudStorageFileTags
      tags:
        - Cloud Storage File Tags
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TagDeleteRequest'
            example:
              ids:
                - '1800000000000101'
                - '1800000000000102'
      responses:
        '200':
          $ref: '#/components/responses/BooleanDataResponse'
components:
  securitySchemes:
    Authorization:
      type: http
      scheme: bearer
      bearerFormat: JWT
  schemas:
    ApiResponseBase:
      type: object
      properties:
        code:
          type: integer
          description: >-
            Mã kết quả. `0` nghĩa là thành công; các mã khác cho biết có ngoại
            lệ.
          example: 0
        msg:
          type:
            - string
            - 'null'
          description: Thông báo lỗi.
          example: null
        requestId:
          type: string
          description: >-
            ID yêu cầu của tác vụ. Trường này có thể xuất hiện khi yêu cầu đi
            qua API gateway.
          example: 1d4f3ea968664593860b94b35d4ebf5e
      required:
        - code
        - msg
    StorageInfo:
      type: object
      properties:
        capacity:
          type: string
          description: Tổng dung lượng cloud storage theo byte.
          example: '10737418240'
        usedCapacity:
          type: string
          description: Dung lượng cloud storage đã dùng theo byte.
          example: '536870912'
        expireDate:
          type: string
          pattern: ^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$
          description: Ngày hết hạn của cloud storage.
        capacityStatus:
          type: integer
          description: 'Trạng thái dung lượng: 0 = bình thường, 1 = đã đầy.'
          example: 0
        isExpired:
          type: boolean
          description: Gói đăng ký đã hết hạn hay chưa.
          example: false
    CloudStorageFile:
      type: object
      properties:
        id:
          type: string
          description: ID tệp trên lưu trữ đám mây.
          example: '1800000000001001'
        fileName:
          type: string
          description: Tên tệp (không kèm phần mở rộng).
          example: example
        fileExt:
          type: string
          description: Phần mở rộng của tệp.
          example: jpg
        fileSize:
          type: string
          description: Kích thước tệp theo byte.
          example: '204800'
        fileType:
          type: integer
          description: >-
            Loại tệp: 0 = hình ảnh, 1 = video, 2 = ứng dụng, 3 = âm thanh, 4 =
            tài liệu, 5 = khác.
          example: 0
        tagInfo:
          type: array
          description: Chi tiết các thẻ đang gắn với tệp.
          items:
            $ref: '#/components/schemas/TagInfo'
        createDate:
          type: string
          pattern: ^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$
          description: Thời điểm tải lên.
        updateDate:
          type: string
          pattern: ^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$
          description: Thời điểm cập nhật.
    TagInfo:
      type: object
      properties:
        id:
          type: string
          description: ID thẻ.
          example: '1800000000000101'
        tagSource:
          type: string
          description: Nguồn của thẻ, ví dụ ENV, PROXY.
          example: ENV
        tagName:
          type: string
          description: Tên thẻ.
          example: Invoice
        tagType:
          type: string
          description: 'Kiểu thẻ: TAG hoặc GROUP.'
          example: TAG
        tagIcon:
          type: string
          description: Biểu tượng của thẻ.
          example: ''
    FilePageRequest:
      type: object
      properties:
        pageNo:
          type: integer
          description: Số trang hiện tại, bắt đầu từ 1. Mặc định là 1.
          minimum: 1
          default: 1
          example: 1
        pageSize:
          type: integer
          description: Số mục mỗi trang. Mặc định là 10.
          minimum: 1
          default: 10
          example: 20
        fileName:
          type: string
          description: Từ khóa tên tệp để tìm kiếm gần đúng.
          example: report
        tagIds:
          type: array
          description: Lọc tệp theo ID thẻ.
          items:
            type: string
          example:
            - '1800000000000101'
        fileType:
          type: integer
          description: >-
            Bộ lọc theo loại tệp: 0 = hình ảnh, 1 = video, 2 = ứng dụng, 3 = âm
            thanh, 4 = tài liệu, 5 = khác.
          example: 0
        fileIds:
          type: array
          description: Lọc tệp theo ID tệp.
          items:
            type: string
          example:
            - '1800000000001001'
    FilePageData:
      type: object
      properties:
        current:
          type: string
          example: '1'
        pages:
          type: string
          example: '7'
        total:
          type: string
          example: '128'
        dataList:
          type: array
          items:
            $ref: '#/components/schemas/CloudStorageFile'
      required:
        - current
        - pages
        - total
        - dataList
    UploadInitRequest:
      type: object
      properties:
        fileNames:
          type: array
          minItems: 1
          description: >-
            Danh sách tên tệp cần tải lên (kèm phần mở rộng), ví dụ photo.jpg,
            video.mp4.
          items:
            type: string
          example:
            - photo.jpg
            - video.mp4
      required:
        - fileNames
    UploadInitResult:
      type: object
      properties:
        id:
          type: string
          description: ID tệp trên lưu trữ đám mây.
          example: '1800000000000001'
        fileName:
          type: string
          description: Tên tệp (kèm phần mở rộng).
          example: photo.jpg
        presignedUrl:
          type: string
          description: >-
            URL tải lên đã ký trước của S3. Dùng HTTPS PUT để tải nội dung tệp
            lên.
          example: https://example.com/presigned-upload-url
    UploadCompleteRequest:
      type: object
      properties:
        id:
          type: string
          description: ID tệp cloud storage do API URL tải lên đã ký trước trả về.
          example: '1800000000000001'
      required:
        - id
    FileDeleteRequest:
      type: object
      properties:
        ids:
          type: array
          minItems: 1
          items:
            type: string
          description: ID các tệp cloud storage cần xóa.
          example:
            - '1800000000001001'
            - '1800000000001002'
      required:
        - ids
    FileTagChangeRequest:
      type: object
      properties:
        fileId:
          type: string
          description: ID tệp trên lưu trữ đám mây.
          example: '1800000000001001'
        tagIds:
          type: array
          minItems: 1
          items:
            type: string
          description: Danh sách ID thẻ.
          example:
            - '1800000000000101'
            - '1800000000000102'
      required:
        - fileId
        - tagIds
    FileTagQueryRequest:
      type: object
      properties:
        fileIds:
          type: array
          minItems: 1
          items:
            type: string
          description: ID các tệp cloud storage.
          example:
            - '1800000000001001'
      required:
        - fileIds
    FileTagResultData:
      type: object
      properties:
        fileId:
          type: string
          description: ID tệp trên lưu trữ đám mây.
          example: '1800000000001001'
        tagIds:
          type: array
          items:
            type: string
          description: ID các thẻ đang gắn với tệp.
          example:
            - '1800000000000101'
        tagInfo:
          type: array
          description: Chi tiết các thẻ.
          items:
            $ref: '#/components/schemas/TagInfo'
    TagCreateRequest:
      type: object
      properties:
        tagName:
          type: string
          description: Tên thẻ.
          example: Invoice
      required:
        - tagName
    TagEditRequest:
      type: object
      properties:
        id:
          type: string
          description: ID thẻ.
          example: '1800000000000101'
        tagName:
          type: string
          description: Tên thẻ.
          example: Invoice 2026
      required:
        - id
        - tagName
    TagDeleteRequest:
      type: object
      properties:
        ids:
          type: array
          minItems: 1
          items:
            type: string
          description: ID các thẻ cần xóa.
          example:
            - '1800000000000101'
      required:
        - ids
  responses:
    StorageInfoResponse:
      description: Thành công
      content:
        application/json:
          schema:
            allOf:
              - $ref: '#/components/schemas/ApiResponseBase'
              - type: object
                properties:
                  data:
                    $ref: '#/components/schemas/StorageInfo'
                required:
                  - data
    FilePageResponse:
      description: Thành công
      content:
        application/json:
          schema:
            allOf:
              - $ref: '#/components/schemas/ApiResponseBase'
              - type: object
                properties:
                  data:
                    $ref: '#/components/schemas/FilePageData'
                required:
                  - data
    UploadInitResponse:
      description: Thành công
      content:
        application/json:
          schema:
            allOf:
              - $ref: '#/components/schemas/ApiResponseBase'
              - type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/UploadInitResult'
                required:
                  - data
    UploadCompleteResponse:
      description: Thành công
      content:
        application/json:
          schema:
            allOf:
              - $ref: '#/components/schemas/ApiResponseBase'
              - type: object
                properties:
                  data:
                    $ref: '#/components/schemas/CloudStorageFile'
                required:
                  - data
    VoidDataResponse:
      description: Thành công
      content:
        application/json:
          schema:
            allOf:
              - $ref: '#/components/schemas/ApiResponseBase'
              - type: object
                properties:
                  data:
                    type: 'null'
                    example: null
    BooleanDataResponse:
      description: Thành công
      content:
        application/json:
          schema:
            allOf:
              - $ref: '#/components/schemas/ApiResponseBase'
              - type: object
                properties:
                  data:
                    type: boolean
                    example: true
                required:
                  - data
    FileTagsResponse:
      description: Thành công
      content:
        application/json:
          schema:
            allOf:
              - $ref: '#/components/schemas/ApiResponseBase'
              - type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/FileTagResultData'
                required:
                  - data
    TagListResponse:
      description: Thành công
      content:
        application/json:
          schema:
            allOf:
              - $ref: '#/components/schemas/ApiResponseBase'
              - type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/TagInfo'
                required:
                  - data
    TagResponse:
      description: Thành công
      content:
        application/json:
          schema:
            allOf:
              - $ref: '#/components/schemas/ApiResponseBase'
              - type: object
                properties:
                  data:
                    $ref: '#/components/schemas/TagInfo'
                required:
                  - data
security:
  - Authorization: []
